#include <buola/gui/x11/auto.h>

#include <buola/gui/cwindow.h>
#include <buola/gui/x11/cfixesregion.h>
#include <buola/gui/x11/cx11display.h>
#include <buola/gui/x11/cx11drawable.h>
#include <buola/gui/x11/cpixmap.h>

#include <X11/extensions/Xfixes.h>

namespace buola { namespace gui { namespace x11 {

const CFixesRegion CFixesRegion::mNull;

CFixesRegion::CFixesRegion()
{
    mID=0;
}

CFixesRegion::CFixesRegion(const CRect_d &pRect)
{
    XRectangle lRect={(int16_t)pRect.l,(int16_t)pRect.t,(uint16_t)pRect.w(),(uint16_t)pRect.h()};
    mID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),&lRect,1);
}

CFixesRegion::CFixesRegion(const CFixesRegion &pRegion)
{
    mID=0;
    Copy(pRegion);
}

CFixesRegion::CFixesRegion(CPixmap *pPixmap,const CPoint_d &pOffset)
{
    mID=XFixesCreateRegionFromBitmap(get_default_x11_display()->GetDisplay(),pPixmap->GetID());
    if(pOffset.x||pOffset.y)
        Translate(pOffset);
}

CFixesRegion::CFixesRegion(Window pWindowID,const CPoint_d &pOffset)
{
    mID=XFixesCreateRegionFromWindow(get_default_x11_display()->GetDisplay(),
                                pWindowID,WindowRegionBounding);
    if(pOffset.x||pOffset.y)
        Translate(pOffset);
}

CFixesRegion::CFixesRegion(TID pID)
{
    mID=pID;
}

CFixesRegion::~CFixesRegion()
{
    if(mID) XFixesDestroyRegion(get_default_x11_display()->GetDisplay(),mID);
}

void CFixesRegion::CreateVoid()
{
    if(mID)
        XFixesSetRegion(get_default_x11_display()->GetDisplay(),mID,nullptr,0);
    else
        mID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),nullptr,0);
}

void CFixesRegion::Subtract(const CRect_d &pRect)
{
    if(mID)
    {
        CFixesRegion lRegion(pRect);
        Subtract(lRegion);
    }
}

void CFixesRegion::Add(const CRect_d &pRect)
{
    if(mID)
    {
        CFixesRegion lRegion(pRect);
        Add(lRegion);
    }
    else
    {
        Copy(pRect);
    }
}

void CFixesRegion::Intersect(const CRect_d &pRect)
{
    if(mID)
    {
        CFixesRegion lRegion(pRect);
        Intersect(lRegion);
    }
}

void CFixesRegion::Subtract(const CFixesRegion &pRegion)
{
    if(!pRegion.mID) return;
    if(mID)
    {
        XFixesSubtractRegion(get_default_x11_display()->GetDisplay(),mID,
                            mID,pRegion.mID);
    }
}

void CFixesRegion::Add(const CFixesRegion &pRegion)
{
    if(!pRegion.mID) return;
    if(mID)
    {
        XFixesUnionRegion(get_default_x11_display()->GetDisplay(),mID,
                        mID,pRegion.mID);
    }
    else
    {
        Copy(pRegion);
    }
}

void CFixesRegion::Intersect(const CFixesRegion &pRegion)
{
    if(mID)
    {
        if(pRegion.mID)
        {
            XFixesIntersectRegion(get_default_x11_display()->GetDisplay(),mID,
                                mID,pRegion.mID);
        }
        else
        {
            XFixesDestroyRegion(get_default_x11_display()->GetDisplay(),mID);
            mID=0;
        }
    }
}

void CFixesRegion::Clear()
{
    if(mID)
    {
        XFixesDestroyRegion(get_default_x11_display()->GetDisplay(),mID);
        mID=0;
    }
}

void CFixesRegion::Set(TID pID)
{
    if(mID) Clear();
    mID=pID;
}

void CFixesRegion::Copy(const CFixesRegion &pRegion)
{
    if(pRegion.mID)
    {
        if(!mID)
            mID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),nullptr,0);
        XFixesCopyRegion(get_default_x11_display()->GetDisplay(),mID,pRegion.mID);
    }
    else if(mID)
    {
        XFixesDestroyRegion(get_default_x11_display()->GetDisplay(),mID);
        mID=0;
    }
}

void CFixesRegion::Copy(const CRect_d &pRect)
{
    XRectangle lRect={(int16_t)pRect.l,(int16_t)pRect.t,(uint16_t)pRect.w(),(uint16_t)pRect.h()};
    if(mID)
        XFixesSetRegion(get_default_x11_display()->GetDisplay(),mID,&lRect,1);
    else
        mID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),&lRect,1);
}

void CFixesRegion::Copy(CPixmap *pPixmap,const CPoint_d &pOffset)
{
    if(mID)
        XFixesDestroyRegion(get_default_x11_display()->GetDisplay(),mID);

    mID=XFixesCreateRegionFromBitmap(get_default_x11_display()->GetDisplay(),pPixmap->GetID());
    if(pOffset.x||pOffset.y)
        Translate(pOffset);
}

void CFixesRegion::Copy(Window pWindow,const CPoint_d &pOffset)
{
    if(mID)
        XFixesDestroyRegion(get_default_x11_display()->GetDisplay(),mID);

    mID=XFixesCreateRegionFromWindow(get_default_x11_display()->GetDisplay(),
                                pWindow,WindowRegionBounding);
    if(pOffset.x||pOffset.y)
        Translate(pOffset);
}

void CFixesRegion::Translate(const CPoint_d &pPoint)
{
    if(mID)
    {
        XFixesTranslateRegion(get_default_x11_display()->GetDisplay(),
                    mID,(int)pPoint.x,(int)pPoint.y);
    }
}

CFixesRegion &CFixesRegion::operator=(const CFixesRegion &pRegion)
{
    Copy(pRegion);
    return (*this);
}

CFixesRegion &CFixesRegion::operator=(const CRect_d &pRect)
{
    Copy(pRect);
    return (*this);
}

CFixesRegion &CFixesRegion::operator-=(const CRect_d &pRect)
{
    Subtract(pRect);
    return (*this);
}

CFixesRegion &CFixesRegion::operator+=(const CRect_d &pRect)
{
    Add(pRect);
    return (*this);
}

CFixesRegion &CFixesRegion::operator*=(const CRect_d &pRect)
{
    Intersect(pRect);
    return (*this);
}

CFixesRegion &CFixesRegion::operator-=(const CFixesRegion &pRegion)
{
    Subtract(pRegion);
    return (*this);
}

CFixesRegion &CFixesRegion::operator+=(const CFixesRegion &pRegion)
{
    Add(pRegion);
    return (*this);
}

CFixesRegion &CFixesRegion::operator*=(const CFixesRegion &pRegion)
{
    Intersect(pRegion);
    return (*this);
}

CFixesRegion CFixesRegion::operator-(const CFixesRegion &pOther) const
{
    TID lNewID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),nullptr,0);
    if(mID&&pOther.mID)
    {
        XFixesSubtractRegion(get_default_x11_display()->GetDisplay(),lNewID,
                            mID,pOther.mID);
    }
    else if(mID)
    {
        XFixesCopyRegion(get_default_x11_display()->GetDisplay(),lNewID,mID);
    }
    return CFixesRegion(lNewID);
}

CFixesRegion CFixesRegion::operator+(const CFixesRegion &pOther) const
{
    TID lNewID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),nullptr,0);
    if(mID&&pOther.mID)
    {
        XFixesUnionRegion(get_default_x11_display()->GetDisplay(),lNewID,
                            mID,pOther.mID);
    }
    else if(mID)
    {
        XFixesCopyRegion(get_default_x11_display()->GetDisplay(),lNewID,mID);
    }
    else if(pOther.mID)
    {
        XFixesCopyRegion(get_default_x11_display()->GetDisplay(),lNewID,pOther.mID);
    }
    return CFixesRegion(lNewID);
}

CFixesRegion CFixesRegion::operator*(const CFixesRegion &pOther) const
{
    TID lNewID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),nullptr,0);
    if(mID&&pOther.mID)
    {
        XFixesIntersectRegion(get_default_x11_display()->GetDisplay(),lNewID,
                            mID,pOther.mID);
    }
    return CFixesRegion(lNewID);
}

CFixesRegion CFixesRegion::operator-(const CRect_d &pOther) const
{
    TID lNewID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),nullptr,0);
    if(mID)
    {
        CFixesRegion lOther(pOther);
        XFixesSubtractRegion(get_default_x11_display()->GetDisplay(),lNewID,
                            mID,lOther.mID);
    }
    return CFixesRegion(lNewID);
}

CFixesRegion CFixesRegion::operator+(const CRect_d &pOther) const
{
    TID lNewID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),nullptr,0);
    if(mID)
    {
        CFixesRegion lOther(pOther);
        XFixesUnionRegion(get_default_x11_display()->GetDisplay(),lNewID,
                            mID,lOther.mID);
    }
    else
    {
        CFixesRegion lOther(pOther);
        XFixesCopyRegion(get_default_x11_display()->GetDisplay(),lNewID,lOther.mID);
    }
    return CFixesRegion(lNewID);
}

CFixesRegion CFixesRegion::operator*(const CRect_d &pOther) const
{
    TID lNewID=XFixesCreateRegion(get_default_x11_display()->GetDisplay(),nullptr,0);
    if(mID)
    {
        CFixesRegion lOther(pOther);
        XFixesIntersectRegion(get_default_x11_display()->GetDisplay(),lNewID,
                            mID,lOther.mID);
    }
    return CFixesRegion(lNewID);
}

void CFixesRegion::Print(io::CTextWriter &pWriter) const
{
    pWriter << "Region:\n";
    if(mID)
    {
        int lN;
        XRectangle *lRects=XFixesFetchRegion(
        get_default_x11_display()->GetDisplay(),mID,&lN);
        for(int i=0;i<lN;i++)
        {
            pWriter << CRect_d(CPoint_d(lRects[i].x,lRects[i].y),
                    CSize_d(lRects[i].width+1,lRects[i].height+1)) << "\n";
        }
        if(lRects) XFree(lRects);
    }
    pWriter << "End region\n";
}

void CFixesRegion::GetRects(std::vector<CRect_d> &pRects) const
{
    pRects.clear();

    if(mID)
    {
        int lN;
        XRectangle *lRects=XFixesFetchRegion(
        get_default_x11_display()->GetDisplay(),mID,&lN);
        for(int i=0;i<lN;i++)
        {
            pRects.push_back(CRect_d(CPoint_d(lRects[i].x,lRects[i].y),
                    CSize_d(lRects[i].width+1,lRects[i].height+1)));
        }
        if(lRects) XFree(lRects);
    }
}

/*namespace x11*/ } /*namespace gui*/ } /*namespace buola*/ }
